/*
 * A simple CORBA banking application
 * Part of the GICOM pedagogical project
 */
package gicom.bank;

import gicom.GenericManager;
import gicom.GenericServer;
import gicom.PersistentObject;
import gicom.config.ObjectConfig;
import gicom.generated.bankServices.Address;
import gicom.generated.bankServices.Bank;
import gicom.generated.bankServices.BankHelper;
import gicom.generated.bankServices.BranchHelper;
import gicom.generated.bankServices.BranchOperations;
import gicom.generated.bankServices.BranchPOATie;
import gicom.generated.bankServices.Customer;
import gicom.generated.bankServices.CustomerHelper;
import gicom.generated.bankServices.Gender;
import gicom.generated.bankServices.ProblemException;
import gicom.generated.transaction.GlobalTransactionManager;
import gicom.generated.transaction.GlobalTransactionManagerHelper;
import gicom.generated.transaction.LocalTransactionManager;
import gicom.generated.transaction.LocalTransactionManagerHelper;
import gicom.transaction.LocalTransactionManagerImpl;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.omg.CORBA.ORB;
import org.omg.CosNaming.NamingContextExt;
import org.omg.CosNaming.NamingContextExtHelper;
import org.omg.PortableServer.POA;
import org.omg.PortableServer.POAPackage.ObjectNotActive;
import org.omg.PortableServer.POAPackage.WrongPolicy;
import org.omg.PortableServer.Servant;

/**
 * The transient Branch implementation
 */
public class BranchImpl extends PersistentObject implements BranchOperations, BranchImplMBean {

    /**
     * The branch address
     */
    private Address address;
    /**
     * The branch name
     */
    private String name = null;
    /**
     * The branch number
     */
    private String number = null;
    /**
     * The bank reference
     */
    private transient Bank bank = null;
    /**
     * The customers of the branch
     */
    private transient Map<String, Customer> customers = null;
    /**
     * The customers implementations of the branch
     */
    private transient Map<String, CustomerImpl> customerImpls = null;
    private static Logger logger = Logger.getLogger(BankImpl.class.getName());
    /**
     * The bank name
     */
    private String bankName = null;
    private LocalTransactionManagerImpl localTMImpl;
    private LocalTransactionManager localTM;
    private GlobalTransactionManager globalTM;

    /**
     * Create a branch
     * 
     * @param name
     *                the branch name
     * @param number
     *                the branch number
     * @param street
     *                the branch street
     * @param city
     *                the branch city
     * @param telephone
     *                the branch telephone
     * @param bank
     *                the bank reference
     */
    public BranchImpl(String name, String number, Address address, Bank bank) {
        super(name);
        this.name = name;
        this.number = number;
        this.address = address;
        this.bank = bank;
        this.bankName = bank.name();

        this.customers = new HashMap<String, Customer>();
        this.customerImpls = new HashMap<String, CustomerImpl>();

    }

    public BranchImpl() {

    }

    /**
     * Get the branch address
     * 
     * @return the branch address
     */
    public Address addr() {
        return this.address;
    }

    /**
     * Create a customer
     * 
     * @param number
     *                the customer's number
     * @param name
     *                the customer's name
     * @param gend
     *                the customer's gender
     * @param addr
     *                the customer's address
     * @return the customer's reference
     * @throws bankServices.ProblemException
     *                 This exception is raised when a problem occurs in the
     *                 banking business methods
     */
    public Customer createCustomer(String number, String name, Gender gend, Address addr) throws ProblemException {
        try {

            String customerId = this.name + ":" + number;
            if (customers.containsKey(customerId)) {
                throw new ProblemException("Customer's number error", "This customer's number already exists");
            }

            CustomerImpl customerImpl = new CustomerImpl(customerId, name, gend, addr, BranchHelper.narrow(getReference()));
            ObjectConfig config = new ObjectConfig(customerId, getPOA().the_name(), CustomerImpl.class.getName(), null, null, null, customerImpl);
            this.createObject(config);

            // Create the customer reference
            Customer customer = CustomerHelper.narrow(customerImpl.getReference());

            // Put the reference of the customer in the map
            customers.put(customerId, customer);
            customerImpls.put(customerId, customerImpl);

            logger.info("create customer " + customerImpl.number() + ":" + customerImpl.name());
            logger.info("create customer " + customer.number() + ":" + customer.name());

            this.save();

            return customer;
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new ProblemException("Failed to create the customer : ", ex.getMessage());
        }

    }

    /**
     * Remove a customer by its number
     * 
     * @param number
     *                the customer's number
     * @throws bankServices.ProblemException
     *                 This exception is raised when a problem occurs in the
     *                 banking business methods
     */
    public void removeCustomer(String number) throws ProblemException {
        try {

            if (customers.containsKey(number)) {
                try {
                    customers.remove(number);
                    CustomerImpl customerImpl = customerImpls.remove(number);
                    customerImpl.destroy();
                } catch (Exception ex) {
                    throw new ProblemException("Error while destroying the customer", ex.getMessage());
                }
            } else {
                throw new ProblemException("Customer's number error", "This customer's number doesn't exist");
            }
            this.save();
        } catch (Exception ex) {
            throw new ProblemException("error while removing customer", ex.getMessage());
        }
    }

    /**
     * Get all customer's references
     * 
     * @return all customer's references
     */
    public Customer[] findAllCustomers() {
        return customers.values().toArray(new Customer[customers.size()]);
    }

    /**
     * Find a customer by his number
     * 
     * @param number
     *                the customer's number
     * @return the customer's reference
     * @throws bankServices.ProblemException
     *                 This exception is raised when a problem occurs in the
     *                 banking business methods
     */
    public Customer findCustomer(String number) throws ProblemException {
        if (customers.containsKey(number)) {
            return (Customer) customers.get(number);
        } else {
            throw new ProblemException("Customer's number error", "This customer's number doesn't exist in this branch");
        }
    }

    /**
     * Get the branch name
     * 
     * @return the branch name
     */
    public String name() {
        return this.getName();
    }

    /**
     * Get the branch number
     * 
     * @return the branch number
     */
    public String number() {
        return this.getNumber();
    }

    /**
     * Get the bank reference
     * 
     * @return the branch reference
     */
    public Bank refBank() {
        return bank;
    }

    public void registerBranch() throws Exception {
        bank.registerBranch(number, BranchHelper.narrow(getReference()));
    }

    public void unregisterBranch() throws Exception {
        bank.unregisterBranch(number);
    }

    public Customer getCustomerReference(String number) {
        return customers.get(number);
    }

    /*
     * Persistence
     */
    private transient String refBank = null;
    private transient String refGTM = null;
    private transient String refLTM = null;
    private transient List<String> custs = null;

    @Override
    public void doReload(ORB orb, POA poa) throws Exception {
        this.bank = BankHelper.narrow(orb.string_to_object(this.refBank));
        this.globalTM = GlobalTransactionManagerHelper.narrow(orb.string_to_object(this.refGTM));
        this.localTM = LocalTransactionManagerHelper.narrow(orb.string_to_object(this.refLTM));
        this.customers = new HashMap<String, Customer>();
        this.customerImpls = new HashMap<String, CustomerImpl>();
        Iterator<String> iter = custs.iterator();
        while (iter.hasNext()) {
            String string = (String) iter.next();
            Customer c = CustomerHelper.narrow(orb.string_to_object(string));
            customers.put(c.number(), c);
        }
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        //
        this.custs = new ArrayList<String>();

        // address
        this.address = (Address) in.readObject();

        // branch name
        this.name = in.readUTF();

        // branch number
        this.number = in.readUTF();

        // bank name
        this.bankName = in.readUTF();

        // bank
        this.refBank = in.readUTF();

        // custumers

        // public Customer createCustomer(String number, String name, Gender
        // gend, Address addr) throws ProblemException

        int count = in.readInt();
        for (int i = 0; i < count; i++) {
            custs.add(in.readUTF());
        }

        this.refGTM = in.readUTF();

        this.refLTM = in.readUTF();

        // id
        this.id = in.readUTF();

    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        try {

            // address
            out.writeObject(this.address);

            // branch name
            out.writeUTF(this.getName());

            // branch number
            out.writeUTF(getNumber());

            // bank name
            out.writeUTF(this.bankName);

            out.writeUTF(getORB().object_to_string(this.bank));

            // custumers
            // public Customer createCustomer(String number, String name, Gender
            // gend, Address addr) throws ProblemException
            out.writeInt(this.customerImpls.size());
            for (Customer c : this.customers.values()) {
                try {
                    String s = getORB().object_to_string(c);
                    out.writeUTF(s);
                } catch (Exception ex) {
                    throw new IOException("Error while writing the customer.", ex);
                }
            }


            // GlobalTM
            out.writeUTF(getORB().object_to_string(this.globalTM));

            // LocalTM
            out.writeUTF(getORB().object_to_string(this.localTM));

            // id
            out.writeUTF(this.id);
        } catch (Exception ex) {
            throw new IOException("Error while writing the object", ex);
        }

    }

    @Override
    protected void doPostCreate(ORB orb, POA poa) throws Exception {
        try {
            NamingContextExt naming = NamingContextExtHelper.narrow(orb.resolve_initial_references("NameService"));
            this.globalTM = GlobalTransactionManagerHelper.narrow(naming.resolve(naming.to_name("GlobalTM")));
            this.localTMImpl = new LocalTransactionManagerImpl(BranchHelper.narrow(poa.id_to_reference(id.getBytes())));
            ObjectConfig config = new ObjectConfig("LocalTM_" + this.name, poa.the_name(), LocalTransactionManagerImpl.class.getName(), null, null, null, this.localTMImpl);
            this.createObject(config);
            this.localTM = LocalTransactionManagerHelper.narrow(localTMImpl.getReference());
        } catch (ObjectNotActive e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (WrongPolicy e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        registerBranch();
        this.save();
    }

    @Override
    protected void doPreDestroy(ORB orb, POA poa) throws Exception {
        GenericServer.defaultGenericServer.getGenericManager().destroyObject("LocalTM_" + this.name);
        unregisterBranch();
    }

    public Servant createServant() {
        return new BranchPOATie(this);
    }

    public String getName() {
        return this.name;
    }

    public String getAddress() {
        return address.street + ", " + address.city + " (" + address.telephone + ")";
    }

    public String getNumber() {
        return number;
    }

    public String getRefBank() {
        return refBank;
    }

    public String toString() {
        return this.bankName + "_" + this.name;
    }

    // TODO : to remove
    public GlobalTransactionManager getGlobalTM() {
        return globalTM;
    }

    @Override
    public LocalTransactionManager localTM() {
        return localTM;
    }

    @Override
    public void desactivate() throws ProblemException {
        try {
            getPOA().deactivate_object(getId().getBytes());
        } catch (Exception ex) {
            throw new ProblemException("error while desactivating object", ex.getMessage());
        }
    }
}
